package com.atguigu.tingshu.common.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.anno.GuiGuCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.AlbumInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@Aspect
public class GuiguCacheAspect {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Around("@annotation(guiguCache)")
    public Object cacheAroundAdvice(ProceedingJoinPoint point  , GuiGuCache guiguCache){
//        //获取参数
//        Long id = Long.parseLong(point.getArgs()[0].toString());
//        //从Redis中查询数据
//        String albumInfoJSOM  = redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + id);
//
//        if (!StringUtils.isEmpty(albumInfoJSOM)){
//            log.info("从Redis缓存中查询到了数据...");
//            return JSON.parseObject(albumInfoJSOM, AlbumInfo.class);
//        }
//
//        //获取到分布式锁
//        RLock rLock = redissonClient.getLock(RedisConstant.ALBUM_LOCK_SUFFIX + id);
//        boolean tryLock = rLock.tryLock();



        String cacheKeyExpression = guiguCache.cacheKey();            // album:info:#{#params[0]}
        String cacheKey = parseExpression(cacheKeyExpression, point, String.class);

        /**
         * 从Redis中获取到的数据，到底需要将其转换成什么类型，取决于目标方法的返回值类型。
         */
        // 获取目标方法的返回值类型。先获取目标方法对象，然后再调用目标方法对象的方法获取目标方法的返回值。
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Type genericReturnType = method.getGenericReturnType();

        // 从Redis的缓存中查询数据
        String json = redisTemplate.opsForValue().get(cacheKey);
        if(!StringUtils.isEmpty(json)) {
            log.info("从Redis中查询到了专辑数据...");
            return JSON.parseObject(json , genericReturnType) ;
        }

        boolean enableLock = guiguCache.enableLock();
        String lockNameExpression = guiguCache.lockName();
        if(enableLock && !StringUtils.isEmpty(lockNameExpression)) {

            String lockName = parseExpression(lockNameExpression, point, String.class);

            // 获取分布式锁
            RLock rLock = redissonClient.getLock(lockName);
            boolean tryLock = rLock.tryLock();
            if(tryLock) {

                log.info("线程{}获取到了分布式锁..." , Thread.currentThread().getId());

                try {

                    // 查询数据库
                    Object result = point.proceed() ;     // 调用目标方法

                    // 把从数据库中查询到的数据存储到Redis中
                    redisTemplate.opsForValue().set(cacheKey , JSON.toJSONString(result) , RedisConstant.ALBUM_TIMEOUT , TimeUnit.SECONDS);

                    // 返回
                    return result ;

                }catch (Throwable e) {
                    throw new RuntimeException(e.getMessage()) ;
                }finally {
                    rLock.unlock();
                    log.info("线程{}释放了分布式锁..." , Thread.currentThread().getId());
                }

            }else {
                log.info("线程{}没有获取到分布式锁...进行自旋" , Thread.currentThread().getId());
                return cacheAroundAdvice(point , guiguCache) ;   // 进行自旋
            }

        }else {

            try {
                // 查询数据库
                Object result = point.proceed() ;     // 调用目标方法

                // 把从数据库中查询到的数据存储到Redis中
                redisTemplate.opsForValue().set(cacheKey , JSON.toJSONString(result) , RedisConstant.ALBUM_TIMEOUT , TimeUnit.SECONDS);

                // 返回
                return result ;
            }catch (Throwable throwable) {
                throw new RuntimeException(throwable.getMessage()) ;
            }

        }

    }

    private static final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    // 表达式解析的解析方法
    private <T> T parseExpression(String expressionStr , ProceedingJoinPoint point , Class<T> clazz) {
        Expression expression = spelExpressionParser.parseExpression(expressionStr, ParserContext.TEMPLATE_EXPRESSION);
        StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext() ;
        standardEvaluationContext.setVariable("params" , point.getArgs());      // 将调用目标方法的实际参数预设给表达式解析器对象
        T value = expression.getValue(standardEvaluationContext, clazz);
        return value ;

    }

}